﻿using System;
using System.Globalization;
using System.Text;
using ChessKit.Properties;

namespace ChessKit.Logic
{
	public static class Fen
	{
		public const string StartPosition = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";

		#region ' ToFenString '

		public static string ToFenString([NotNull] this ProtoBoard board)
		{
			var fen = new StringBuilder(77);
			AppendSquares(board, fen);
			AppendSideToMove(board, fen);
			AppendCastlingAvailability(board, fen);
			AppendEnPassantFile(board, fen);
			AppendFiftyMoveClock(board, fen);
			AppendMoveNumber(board, fen);
			return fen.ToString();
		}

		private static void AppendSquares(ProtoBoard board, StringBuilder fen)
		{
			for (var rank = 7; rank >= 0; rank--)
			{
				var emptyCounter = 0;
				for (var file = 0; file <= 7; file++)
				{
					var idx = new Square(file, rank);
					if (board.Squares[idx] == 0)
					{
						emptyCounter++;
					}
					else
					{
						if (emptyCounter != 0) fen.Append(emptyCounter);
						fen.Append(board[idx].ToEnglishSymbol());
						emptyCounter = 0;
					}
				}
				if (emptyCounter != 0) fen.Append(emptyCounter);
				if (rank != 0) fen.Append('/');
			}
		}
		private static void AppendSideToMove(ProtoBoard board, StringBuilder fen)
		{
			fen.Append(' ');
			fen.Append(board.SideOnMove == PieceColor.White ? 'w' : 'b');
		}
		private static void AppendCastlingAvailability(ProtoBoard board, StringBuilder fen)
		{
			fen.Append(' ');
			var castling = board.CastlingAvailability;
			if (castling == CastlingAvailability.None)
			{
				fen.Append('-');
			}
			else
			{
				if ((castling & CastlingAvailability.WhiteKing) != 0) fen.Append('K');
				if ((castling & CastlingAvailability.WhiteQueen) != 0) fen.Append('Q');
				if ((castling & CastlingAvailability.BlackKing) != 0) fen.Append('k');
				if ((castling & CastlingAvailability.BlackQueen) != 0) fen.Append('q');
			}
		}
		private static void AppendEnPassantFile(ProtoBoard board, StringBuilder fen)
		{
			fen.Append(' ');
			if (!board.EnPassantFile.HasValue)
			{
				fen.Append('-');
			}
			else
			{
				fen.Append("abcdefgh"[board.EnPassantFile.GetValueOrDefault()]);
				fen.Append(board.SideOnMove == PieceColor.White ? '6' : '3');
			}
		}
		private static void AppendFiftyMoveClock(ProtoBoard board, StringBuilder fen)
		{
			fen.Append(' ');
			fen.Append(board.FiftyMoveClock);
		}
		private static void AppendMoveNumber(ProtoBoard board, StringBuilder fen)
		{
			fen.Append(' ');
			fen.Append(board.MoveNumber);
		}

		#endregion

		#region ' FromFenString '

		/// <summary> Load position from FEN string </summary>
		/// <param name="fen">FEN string</param>
		/// <remarks>http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation</remarks>
		public static ProtoBoard FromFenString([NotNull] string fen)
		{
			if (fen == null) throw new ArgumentNullException("fen");
			var offset = 0;
			var board = new BoardBuilder();
			ParseSquares(board, fen, ref offset);
			ParseSideOnMove(board, fen, ref offset);
			ParseLoadCastlingAvailability(board, fen, ref offset);
			ParseEnPassant(board, fen, ref offset);
			ParseFiftymoveClock(board, fen, ref offset);
			ParseFullmoveNumber(board, fen, ref offset);
			return board.ToProtoBoard();
		}

		private static void ParseSquares(BoardBuilder builder, string fen, ref int i)
		{
			for (var sq = 63; ; i++)
			{
				if (fen[i] == ' ') break;
				if (fen[i] >= '1' && fen[i] <= '9') sq -= fen[i] - '0';
				else if ('/' == fen[i])
				{
				}
				else
				{
					var c = (sq / 8) * 8 + 7 - sq % 8;
					builder[(byte)(c + (c & ~7))] = fen[i].EnglishSymbolToPiece();
					sq--;
				}
			}
			i++; // Skip the space
		}
		private static void ParseSideOnMove(BoardBuilder builder, string fen, ref int i)
		{
			builder.SideOnMove = ParseColor(fen[i++]);
			//      if (fen[i] != ' ')
			//        throw new FormatException("Unexpected symbol");
			i++; // skip the space 
		}
		private static void ParseLoadCastlingAvailability(BoardBuilder builder, string fen, ref int i)
		{
			var flags = default(CastlingAvailability);
			for (; ; i++)
			{
				if (fen[i] == '-')
				{
				}
				else if (fen[i] == 'K') flags |= CastlingAvailability.WhiteKing;
				else if (fen[i] == 'Q') flags |= CastlingAvailability.WhiteQueen;
				else if (fen[i] == 'k') flags |= CastlingAvailability.BlackKing;
				else if (fen[i] == 'q') flags |= CastlingAvailability.BlackQueen;
				else if (fen[i] == ' ') break;
				//        else throw new FormatException("illegal character");
			}
			builder.CastlingAvailability = flags;
			i++; // Skip the space
		}
		private static void ParseEnPassant(BoardBuilder builder, string fen, ref int i)
		{
			if (fen[i] == '-')
			{
				i += 2; // skip the '-' and space
			}
			else
			{
				builder.EnPassantFile = ParseFile(fen[i]);
				i += 3; // skip the file, rank, and space
			}
		}

		private static void ParseFiftymoveClock(BoardBuilder builder, string fen, ref int i)
		{
			var res = 0;
			for (; ; i++)
			{
				if (fen[i] == ' ') break;
				if (fen[i] >= '0' && fen[i] <= '9')
					res = res * 10 + fen[i] - '0';
			}
			builder.FiftyMoveClock = res;
			i++; // Skip the space
		}
		private static void ParseFullmoveNumber(BoardBuilder builder, string fen, ref int i)
		{
			var res = 0;
			for (; i < fen.Length; i++)
				if (fen[i] >= '0' && fen[i] <= '9')
					res = res * 10 + fen[i] - '0';
			builder.MoveNumber = res;
		}
		private static PieceColor ParseColor(char symbol)
		{
			switch (symbol)
			{
				case 'W':
				case 'w':
					return PieceColor.White;
				case 'B':
				case 'b':
				default:
					return PieceColor.Black;
				//          default:
				//            throw new FormatException("What color is that?");
			}
		}
		private static int ParseFile(char symbol)
		{
			return Char.ToLower(symbol, CultureInfo.InvariantCulture) - 'a';
		}

		#endregion
	}
}
